#include "zutil.h"
#include "inftrees.h"
#include "inflate.h"
#include "inffast.h"

#ifndef ASMINF

#ifdef POSTINC
#  define OFF 0
#  define PUP(a) *(a)++
#else
#  define OFF 1
#  define PUP(a) *++(a)
#endif

void inflate_fast( strm, start )
z_streamp strm;
unsigned start;         /* inflate()'s starting value for strm->avail_out */
{
  struct inflate_state FAR *state;
  unsigned char FAR *in;      /* local strm->next_in */
  unsigned char FAR *last;    /* while in < last, enough input available */
  unsigned char FAR *out;     /* local strm->next_out */
  unsigned char FAR *beg;     /* inflate()'s initial strm->next_out */
  unsigned char FAR *end;     /* while out < end, enough space available */
  #ifdef INFLATE_STRICT
  unsigned dmax;              /* maximum distance from zlib header */
  #endif
  unsigned wsize;             /* window size or zero if not using window */
  unsigned whave;             /* valid bytes in the window */
  unsigned write;             /* window write index */
  unsigned char FAR *window;  /* allocated sliding window, if wsize != 0 */
  unsigned long hold;         /* local strm->hold */
  unsigned bits;              /* local strm->bits */
  code const FAR *lcode;      /* local strm->lencode */
  code const FAR *dcode;      /* local strm->distcode */
  unsigned lmask;             /* mask for first level of length codes */
  unsigned dmask;             /* mask for first level of distance codes */
  code this;                  /* retrieved table entry */
  unsigned op;                /* code bits, operation, extra bits, or */
  /*  window position, window bytes to copy */
  unsigned len;               /* match length, unused bytes */
  unsigned dist;              /* match distance */
  unsigned char FAR *from;    /* where to copy match from */
  /* copy state to local variables */
  state = ( struct inflate_state FAR * )strm->state;
  in = strm->next_in - OFF;
  last = in + ( strm->avail_in - 5 );
  out = strm->next_out - OFF;
  beg = out - ( start - strm->avail_out );
  end = out + ( strm->avail_out - 257 );
  #ifdef INFLATE_STRICT
  dmax = state->dmax;
  #endif
  wsize = state->wsize;
  whave = state->whave;
  write = state->write;
  window = state->window;
  hold = state->hold;
  bits = state->bits;
  lcode = state->lencode;
  dcode = state->distcode;
  lmask = ( 1U << state->lenbits ) - 1;
  dmask = ( 1U << state->distbits ) - 1;
  do {
    if( bits < 15 ) {
      hold += ( unsigned long )( PUP( in ) ) << bits;
      bits += 8;
      hold += ( unsigned long )( PUP( in ) ) << bits;
      bits += 8;
    }
    this = lcode[hold & lmask];
  dolen:
    op = ( unsigned )( this.bits );
    hold >>= op;
    bits -= op;
    op = ( unsigned )( this.op );
    if( op == 0 ) {                         /* literal */
      Tracevv( ( stderr, this.val >= 0x20 && this.val < 0x7f ?
                 "inflate:         literal '%c'\n" :
                 "inflate:         literal 0x%02x\n", this.val ) );
      PUP( out ) = ( unsigned char )( this.val );
    } else if( op & 16 ) {                  /* length base */
      len = ( unsigned )( this.val );
      op &= 15;                           /* number of extra bits */
      if( op ) {
        if( bits < op ) {
          hold += ( unsigned long )( PUP( in ) ) << bits;
          bits += 8;
        }
        len += ( unsigned )hold & ( ( 1U << op ) - 1 );
        hold >>= op;
        bits -= op;
      }
      Tracevv( ( stderr, "inflate:         length %u\n", len ) );
      if( bits < 15 ) {
        hold += ( unsigned long )( PUP( in ) ) << bits;
        bits += 8;
        hold += ( unsigned long )( PUP( in ) ) << bits;
        bits += 8;
      }
      this = dcode[hold & dmask];
    dodist:
      op = ( unsigned )( this.bits );
      hold >>= op;
      bits -= op;
      op = ( unsigned )( this.op );
      if( op & 16 ) {                     /* distance base */
        dist = ( unsigned )( this.val );
        op &= 15;                       /* number of extra bits */
        if( bits < op ) {
          hold += ( unsigned long )( PUP( in ) ) << bits;
          bits += 8;
          if( bits < op ) {
            hold += ( unsigned long )( PUP( in ) ) << bits;
            bits += 8;
          }
        }
        dist += ( unsigned )hold & ( ( 1U << op ) - 1 );
        #ifdef INFLATE_STRICT
        if( dist > dmax ) {
          strm->msg = ( char * )"invalid distance too far back";
          state->mode = BAD;
          break;
        }
        #endif
        hold >>= op;
        bits -= op;
        Tracevv( ( stderr, "inflate:         distance %u\n", dist ) );
        op = ( unsigned )( out - beg ); /* max distance in output */
        if( dist > op ) {               /* see if copy from window */
          op = dist - op;             /* distance back in window */
          if( op > whave ) {
            strm->msg = ( char * )"invalid distance too far back";
            state->mode = BAD;
            break;
          }
          from = window - OFF;
          if( write == 0 ) {          /* very common case */
            from += wsize - op;
            if( op < len ) {        /* some from window */
              len -= op;
              do {
                PUP( out ) = PUP( from );
              } while( --op );
              from = out - dist;  /* rest from output */
            }
          } else if( write < op ) {   /* wrap around window */
            from += wsize + write - op;
            op -= write;
            if( op < len ) {        /* some from end of window */
              len -= op;
              do {
                PUP( out ) = PUP( from );
              } while( --op );
              from = window - OFF;
              if( write < len ) { /* some from start of window */
                op = write;
                len -= op;
                do {
                  PUP( out ) = PUP( from );
                } while( --op );
                from = out - dist;      /* rest from output */
              }
            }
          } else {                    /* contiguous in window */
            from += write - op;
            if( op < len ) {        /* some from window */
              len -= op;
              do {
                PUP( out ) = PUP( from );
              } while( --op );
              from = out - dist;  /* rest from output */
            }
          }
          while( len > 2 ) {
            PUP( out ) = PUP( from );
            PUP( out ) = PUP( from );
            PUP( out ) = PUP( from );
            len -= 3;
          }
          if( len ) {
            PUP( out ) = PUP( from );
            if( len > 1 )
            { PUP( out ) = PUP( from ); }
          }
        } else {
          from = out - dist;          /* copy direct from output */
          do {                        /* minimum length is three */
            PUP( out ) = PUP( from );
            PUP( out ) = PUP( from );
            PUP( out ) = PUP( from );
            len -= 3;
          } while( len > 2 );
          if( len ) {
            PUP( out ) = PUP( from );
            if( len > 1 )
            { PUP( out ) = PUP( from ); }
          }
        }
      } else if( ( op & 64 ) == 0 ) {     /* 2nd level distance code */
        this = dcode[this.val + ( hold & ( ( 1U << op ) - 1 ) )];
        goto dodist;
      } else {
        strm->msg = ( char * )"invalid distance code";
        state->mode = BAD;
        break;
      }
    } else if( ( op & 64 ) == 0 ) {         /* 2nd level length code */
      this = lcode[this.val + ( hold & ( ( 1U << op ) - 1 ) )];
      goto dolen;
    } else if( op & 32 ) {                  /* end-of-block */
      Tracevv( ( stderr, "inflate:         end of block\n" ) );
      state->mode = TYPE;
      break;
    } else {
      strm->msg = ( char * )"invalid literal/length code";
      state->mode = BAD;
      break;
    }
  } while( in < last && out < end );
  /* return unused bytes (on entry, bits < 8, so in won't go too far back) */
  len = bits >> 3;
  in -= len;
  bits -= len << 3;
  hold &= ( 1U << bits ) - 1;
  /* update state and return */
  strm->next_in = in + OFF;
  strm->next_out = out + OFF;
  strm->avail_in = ( unsigned )( in < last ? 5 + ( last - in ) : 5 - ( in - last ) );
  strm->avail_out = ( unsigned )( out < end ?
                                  257 + ( end - out ) : 257 - ( out - end ) );
  state->hold = hold;
  state->bits = bits;
  return;
}


#endif
